package baseFx.database.monitor;

import baseFx.common.utils.StringUtils;
import baseFx.database.where.ExecuteWhere;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class WatchSetting {
    private List<Def> details = new ArrayList<>();
    private Map<Class, List<Def>> detailsByTable = new HashMap<>();
    private Map<Integer, Boolean> existsCache = new HashMap<>();

    public void load(Class type) {
        if (type.isAnnotationPresent(DbWatcher.class) == false) {
            return;
        }
        for (Method method : type.getDeclaredMethods()) {
            Before before = method.getAnnotation(Before.class);
            After after = method.getAnnotation(After.class);
            if (before == null && after == null) {
                continue;
            }
            if (before != null) {
                Def detail = new Def();
                detail.isBefore = true;
                detail.method = method;
                detail.operation = before.value();
                detail.table = before.table();
                detail.serviceType = type;
                add(detail);
            }
            if (after != null) {
                Def detail = new Def();
                detail.isBefore = false;
                detail.method = method;
                detail.operation = after.value();
                detail.table = after.table();
                detail.serviceType = type;
                add(detail);
            }
        }
    }

    private void check(Def detail) {
        if (detail.method.isAccessible() == false) {
            detail.method.setAccessible(true);
        }
        String pref = "[" + (detail.table != void.class ? detail.table.getSimpleName() + "." : "") + detail.operation.name() + "]监听方法" +
                detail.serviceType.getCanonicalName() + "." + detail.method.getName();
        Parameter[] ps = detail.method.getParameters();
        if (ps.length < 1) {
            return;
        }
        if (detail.table != void.class) {
            switch (detail.operation) {
                case insert:
                    if (ps.length != 1 || ps[0].getType().isAssignableFrom(detail.table) == false) {
                        String msg = StringUtils.format("{}形参错误，正确形参：({})", pref, detail.table.getSimpleName());
                        throw new RuntimeException(msg);
                    }
                    break;
                case delete:
                    if (ps.length != 1 || ps[0].getType() != ExecuteWhere.class) {
                        String msg = StringUtils.format("{}形参错误，正确形参：({})", pref, ExecuteWhere.class.getSimpleName());
                        throw new RuntimeException(msg);
                    }
                    break;
                case setNull:
                case update:
                    if (ps.length != 2 || ps[0].getType() != detail.table || ps[1].getType() != ExecuteWhere.class) {
                        String msg = StringUtils.format("{}形参错误，正确形参：({}, {})", pref, detail.table.getSimpleName(), ExecuteWhere.class.getSimpleName());
                        throw new RuntimeException(msg);
                    }
                    break;
                default:
                    throw new RuntimeException("无法识别的类型：" + detail.operation.name());
            }
        } else {
            switch (detail.operation) {
                case insert:
                    if (!(ps.length == 2 && ps[0].getType() == Class.class && ps[1].getType() == Object.class)) {
                        throw new RuntimeException(pref + "形参错误，正确形参：(Class, Object)");
                    }
                    break;
                case update:
                case setNull:
                    if (!(ps.length == 3 && ps[0].getType() == Class.class && ps[1].getType() == Object.class && ps[2].getType() == ExecuteWhere.class)) {
                        throw new RuntimeException(pref + "形参错误，正确形参：(Class， Object， ExecuteWhere)");
                    }
                    break;
                case delete:
                    if (!(ps.length == 2 && ps[0].getType() == Class.class && ps[1].getType() == ExecuteWhere.class)) {
                        throw new RuntimeException(pref + "形参错误，正确形参：(Class， Object)");
                    }
                    break;
                default:
                    throw new RuntimeException("无法识别的类型：" + detail.operation.name());
            }
        }
    }

    private void add(Def item) {
        check(item);
        if (detailsByTable.containsKey(item.table) == false) {
            detailsByTable.put(item.table, new ArrayList<>());
        }
        detailsByTable.get(item.table).add(item);
        details.add(item);
    }

    public void clear() {
        detailsByTable.clear();
        existsCache.clear();
        details.clear();
        details = null;
        existsCache = null;
        detailsByTable = null;
    }

    public List<Def> gets(boolean isBefore, Class table, WatchOperation operation) {
        List<Def> list = new ArrayList<>();
        if (detailsByTable.containsKey(table)) {
            for (Def item : detailsByTable.get(table)) {
                if (item.isBefore == isBefore && item.operation == operation) {
                    list.add(item);
                }
            }
        }
        if (detailsByTable.containsKey(void.class)) {
            for (Def item : detailsByTable.get(void.class)) {
                if (item.isBefore == isBefore && item.operation == operation) {
                    list.add(item);
                }
            }
        }
        return list;
    }

    public boolean exists(boolean isBefore, Class table, WatchOperation operation) {
        int code = hashCode(isBefore, table, operation);
        if (existsCache.containsKey(code) == false) {
            boolean ets = false;
            if (table != Void.class && detailsByTable.containsKey(table)) {
                for (Def item : detailsByTable.get(table)) {
                    if (item.isBefore == isBefore && item.operation == operation) {
                        ets = true;
                        break;
                    }
                }
            }
            if (ets == false && detailsByTable.containsKey(void.class)) {
                for (Def item : detailsByTable.get(void.class)) {
                    if (item.isBefore == isBefore && item.operation == operation) {
                        ets = true;
                        break;
                    }
                }
            }
            existsCache.put(code, ets);
        }
        return existsCache.get(code);
    }

    private int hashCode(boolean isBefore, Class table, WatchOperation operation) {
        int code = table.hashCode();
        code = 31 * code + (isBefore ? 0 : 1);
        code = 31 * code + operation.hashCode();
        return code;
    }

    class Def {
        Class serviceType;
        Class table;
        Method method;
        WatchOperation operation;
        boolean isBefore;
    }
}
